#include <strmif.h>
#include "mkvcontext.hpp"
#include "mkvstreamaudioac3.hpp"
#include "cmediatypes.hpp"
#include <cassert>
#include <uuids.h>
#ifdef _DEBUG
#include <odbgstream.hpp>
#include <iomanip>
using std::endl;
using std::hex;
using std::dec;
using std::setfill;
using std::setw;
#endif

//Audio Substream Headers
//http://www.mpucoder.com/DVD/ass-hdr.html
//
//AC3 frames are 32ms in length, or 2880 ticks of the 90KHz clock. The resulting 
//frame rate is 31.25 fps.
//
//AC3 (Dolby Digital) Headers
//http://www.mpucoder.com/DVD/ac3hdr.html
//
//Digital Audio Compression (AC-3, E-AC3) Standard, Rev. B
//http://www.atsc.org/standards/a_52b.pdf
//
//AC-3 Compressed Audio (Dolby Digital), Revision A:
//http://www.digitalpreservation.gov/formats/fdd/fdd000209.shtml
//
//http://web.archive.org/web/20060207093602/http://www.sparta.lu.se/~bjorn/whitney/references/ac3spec.pdf
//Chapter 5 (p. 12) describes Sync Frame.

namespace MkvMux
{


void MkvStreamAudioAc3::GetMediaTypes(CMediaTypes& mtv)
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Audio;
    mt.subtype = MEDIASUBTYPE_DOLBY_AC3;
    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    mtv.Add(mt);
}


bool MkvStreamAudioAc3::QueryAccept(const AM_MEDIA_TYPE& mt)
{
    //http://msdn.microsoft.com/en-us/library/ff536236(VS.85).aspx
    //Converting Between Format Tags and Subformat GUIDs
    //
    //The guidelines for handling non-PCM WAVE_FORMAT_EXTENSIBLE 
    //formats are similar to those for non-PCM formats that are 
    //specified by wave-format tags. Specifically, a 
    //WAVE_FORMAT_EXTENSIBLE format should have a pin factory 
    //separate from the factory for PCM formats, and it requires 
    //its own data-range intersection handler.
    //
    //The audio format for a WAVE_FORMAT_EXTENSIBLE format is 
    //specified by the GUID in the SubFormat member of the 
    //KSDATAFORMAT structure. Every registered wave-format tag 
    //has a corresponding subformat GUID, which is generated by 
    //the DEFINE_WAVEFORMATEX_GUID macro in Ksmedia.h. For example, 
    //the GUID corresponding to the WAVE_FORMAT_DOLBY_AC3_SPDIF tag 
    //is defined as DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_DOLBY_AC3_SPDIF).

    //The following value seems to be defined on the Internet:
    //WAVE_FORMAT_AC3 0x2000 (8192)
    //but I don't think Microsoft has defined this format tag.
    //I think Ac3Filter uses this, and the Matroska codec page says
    //to use it too.  I haven't bothered checking that value here,
    //since the use of media subtype GUID and a WAVEFORMATEXTENSIBLE 
    //structure is a more modern approach.
    
    //See also "Supporting Non-PCM Wave Formats":
    //http://msdn.microsoft.com/en-us/library/ff538408(v=VS.85).aspx
    //
    //See also: "Specifying AC-3 Data Ranges":
    //http://msdn.microsoft.com/en-us/library/ff538357(v=VS.85).aspx
    //
    //See also: "Subformat GUIDs for Compressed Audio Formats" (Win7):
    //http://msdn.microsoft.com/en-us/library/ff538392(v=VS.85).aspx
    //
    //For Windows 7, new subformat GUIDs have been added to the 
    //Ksmedia.h header file to provide support for compressed audio 
    //formats. Subformat GUIDs indicate the specific subformat of a 
    //data format. These formats are defined by the Consumer 
    //Electronics Association (CEA) standard for uncompressed audio 
    //(CEA-861-D). 
    //
    //00000092-0000-0010-8000-00aa00389b71  = AC-3
    //KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL    
    //CEA 861 type = 0x02
    //
    //MJH: Note that this corresponds to format tag
    //WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
    //which is defined by Microsoft.  I don't know where
    //the format tag
    //WAVE_FORMAT_AC3 0x2000
    //came from.  It sure looks like someone just made it up.
    //END MJH.
    //
    //See also: "Representing Formats for IEC 61937 Transmissions":
    //http://msdn.microsoft.com/en-us/library/dd316761(VS.85).aspx
    //
    //With the increase in media storage devices that require 
    //compressed audio formats, applications must identify, describe, 
    //and use a variety of new encoded audio content for transmitting 
    //content from PCs to devices such as HDMI or DisplayPort receiver.
    //
    //To represent an encoded audio stream to be transmitted over an 
    //IEC 61937-compatible interface, an application must provide: 
    // *The characteristics of an encoded audio stream to be transmitted. 
    // *The characteristics of a decoded audio stream on the target device. 
    //
    //In Windows Vista and earlier Windows operating systems, an application 
    //can infer the quality level of an audio format from the number of 
    //channels, the sample size, and the data rate of an audio stream that 
    //uses the format. For a PCM format, this information is available from 
    //the nChannels, nSamplesPerSec, and nAvgBytesPerSec members of the 
    //WAVEFORMATEX structure that specifies the format. For a non-PCM format, 
    //these three members have been commandeered to store information about 
    //the compressed data in the audio stream. Thus, the WAVEFORMATEX 
    //structure lacks information about the effective number of channels, 
    //sample size, and data rate of the non-PCM audio stream after the stream 
    //is decompressed and played. Based on the information in this structure, 
    //a user or an application might have difficulty inferring the quality 
    //level of the non-PCM stream.
    //
    //The WAVEFORMATEX was extended to the WAVEFORMATEXTENSIBLE structure 
    //to provide the extra stream characteristics. However, this structure 
    //is also not adequate in describing the stream for IEC 61937 transmissions 
    //because it was intended to represent a single set of characteristics 
    //and used for uncompressed, multi-channel PCM data. 
    //
    //In Windows 7, the operating system addresses this problem by providing 
    //support for a new structure, WAVEFORMATEXTENSIBLE_IEC61937 which extends 
    //WAVEFORMATEXTENSIBLE structure to store two sets of audio stream 
    //characteristics: the encoded audio format before transmission and 
    //characteristics of the audio stream after it has been decoded. 
    //The new structure explicitly specifies the effective number of channels, 
    //sample size, and data rate of a non-PCM format. With this information, 
    //an application can infer the quality level of the non-PCM stream after 
    //it is decompressed and played. 
    //LOTS MORE INTERESTING INFO AT THAT PAGE...
    //
    //See also:
    //Microsoft DirectShow 9.0 
    //MPEG-2 Splitter Media Types
    //ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/directshow/htm/mpeg2splitterfiltermediatypes.htm
    //http://msdn.microsoft.com/en-us/library/dd390722(VS.85).aspx
    //
    //The MPEG-2 Splitter filter currently supports audio and video. 
    //Dolby AC-3 is supported as a substream as defined by DVD. The filter 
    //also supports MPEG-2 audio. The media types depend on whether the 
    //MPEG-2 splitter is delivering PES packets or PES payloads.
    //
    //AC-3 Audio
    //For AC-3 audio, the media types are as follows.
    //                 PES output             Payload output 
    //Major Type       MEDIATYPE_MPEG2_PES    MEDIATYPE_Audio 
    //Subtype          MEDIASUBTYPE_DOLBY_AC3 MEDIASUBTYPE_DOLBY_AC3 
    //Format Type      FORMAT_WaveFormatEx    FORMAT_WaveFormatEx 
    //Format Structure WAVEFORMATEX           WAVEFORMATEX 
    //
    //The WAVEFORMATEX structure's wFormatTag member for AC-3 is 
    //currently WAVE_FORMAT_UNKNOWN, but this might change.    

    if (mt.majortype != MEDIATYPE_Audio)
        return false;
        
    if (mt.subtype != MEDIASUBTYPE_DOLBY_AC3)
        return false;
        
    if (mt.formattype != FORMAT_WaveFormatEx)
        return false;
    
    if (mt.cbFormat < sizeof(WAVEFORMATEX))
        return false;
        
    if (mt.pbFormat == 0)
        return false;

    const WAVEFORMATEX& wfx = (WAVEFORMATEX&)(*mt.pbFormat);
    wfx;    

    //TODO: anything else here?
    
    return true;
}


HRESULT MkvStreamAudioAc3::GetAllocatorRequirements(
    const AM_MEDIA_TYPE&,
    ALLOCATOR_PROPERTIES& props)
{
    props.cBuffers = 2;  //TODO: why 2 here?
    props.cbBuffer = 0;
    props.cbAlign = 0;
    props.cbPrefix = 0;

    return S_OK;
}


MkvStreamAudio* MkvStreamAudioAc3::CreateStream(
    MkvContext& ctx,
    const AM_MEDIA_TYPE& mt)
{
   assert(mt.majortype == MEDIATYPE_Audio);
   assert(mt.formattype == FORMAT_WaveFormatEx);
   
   const ULONG cb = mt.cbFormat;
   assert(cb >= sizeof(WAVEFORMATEX));

   const BYTE* const pb = mt.pbFormat;
   assert(pb);
   
   return new (std::nothrow) MkvStreamAudioAc3(ctx, pb, cb);
}


MkvStreamAudioAc3::Ac3Frame::Ac3Frame(Ac3Parser::Frame* f) :
    m_pFrame(f)
{
}


MkvStreamAudioAc3::Ac3Frame::~Ac3Frame()
{
   Ac3Parser::Frame::Destroy(m_pFrame);
}


ULONG MkvStreamAudioAc3::Ac3Frame::curr_timecode_ms() const
{
   return m_pFrame->timecode_ms();
}


ULONG MkvStreamAudioAc3::Ac3Frame::size() const
{
   return m_pFrame->buflen();
}


const BYTE* MkvStreamAudioAc3::Ac3Frame::data() const
{
   return m_pFrame->buf();
}


MkvStreamAudioAc3::MkvStreamAudioAc3(
    MkvContext& ctx,
    const BYTE* pb,
    ULONG cb) :
    MkvStreamAudio(ctx, pb, cb)
{
}


const WAVEFORMATEX& MkvStreamAudioAc3::GetFormat() const
{
    ULONG cb;
    const void* const pv = MkvStreamAudio::GetFormat(cb);
    assert(pv);
    assert(cb >= 18);
    
    const WAVEFORMATEX* const pwfx = static_cast<const WAVEFORMATEX*>(pv);
    assert(pwfx);
    assert(cb >= (18 + ULONG(pwfx->cbSize)));
    
    const WAVEFORMATEX& wfx = *pwfx;
    return wfx;
}


ULONG MkvStreamAudioAc3::GetSamplesPerSec() const
{
    const WAVEFORMATEX& wfx = GetFormat();
    assert(wfx.nSamplesPerSec > 0);

    return wfx.nSamplesPerSec;
}


BYTE MkvStreamAudioAc3::GetChannels() const
{
    const WAVEFORMATEX& wfx = GetFormat();
    assert(wfx.nChannels > 0);
    assert(wfx.nChannels <= 255);

    return static_cast<BYTE>(wfx.nChannels);
}


//void MkvStreamAudioAc3::WriteTrackName()
//{
//    EbmlIO::File& f = m_context.m_file;
//
//    f.WriteID2(0x536E);   //name
//    f.Write1UTF8(L"AC-3 audio stream");
//}


void MkvStreamAudioAc3::WriteTrackCodecID()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID1(0x86);  //Codec ID
    f.Write1String("A_AC3");    //TODO

    //TODO:    
    //http://www.matroska.org/technical/specs/codecid/index.html
    //
    //BSID <= 8 !! The private data is void ??? 
    //  Corresponding ACM wFormatTag : 0x2000 ; channel number have to be read 
    //  from the corresponding audio element.
    //
    //A_AC3/BSID9
    //A_AC3/BSID10 
    //
    //AC3/BSID9 and AC3/BSID10 (DolbyNet) : 
    //The ac3 frame header has, similar to the mpeg-audio header a version field. 
    //Normal ac3 is defined as bitstream id 8 (5 Bits, numbers are 0-15). 
    //Everything below 8 is still compatible with all decoders that handle 8 
    //correctly. Everything higher are additions that break decoder compatibility.
    //For the samplerates 24kHz (00); 22,05kHz (01) and 16kHz (10) the BSID is 9.
    //For the samplerates 12kHz (00); 11,025kHz (01) and 8kHz (10) the BSID is 10.
    //END TODO    
}


void MkvStreamAudioAc3::WriteTrackCodecName()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID3(0x258688);  //Codec Name
    f.Write1UTF8(L"AC-3");
}



HRESULT MkvStreamAudioAc3::Receive(IMediaSample* pSample)
{
    assert(pSample);

    if (m_context.m_file.GetStream() == 0)
        return S_OK;  //TODO: send EOS immediately (and return S_FALSE)

    BYTE* ptr;

    HRESULT hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    hr;
         
    const long len = pSample->GetActualDataLength();
    assert(len >= 0);

    m_parser.Enqueue(ptr, len);

    while (Ac3Parser::Frame* f = m_parser.Dequeue())
    {
      //We might have to create a generic "audio frame" class
      //or just "frame" class containing audio-specific things,
      //generic so that it can be shared among all possible
      //audio input streams.  Here the Ac3Parser::Frame type
      //could just derive from this abstract base class.
      //We could use refcounting the same as we do for 
      //dshow types.

      //Alternatively, at the point where we make the
      //decision to write the frame (decide which cluster),
      //we could just guery the stream an opaque way, 
      //such as "what is your earliest time", and "write
      //a frame now".  So the context would talke to the
      //stream, instead of dealing with frames directly.
      //This has the benefit of isolating parser details
      //(e.g. Ac3Parser::Frame) for a stream from muxer details.

      Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
      assert(pFrame);  //TODO

      m_context.NotifyAudioFrame(this, pFrame);
    }
        
    return S_OK;
}


int MkvStreamAudioAc3::EndOfStream()
{
    if (m_context.m_file.GetStream())
    {
        m_parser.EndOfStream();

        while (Ac3Parser::Frame* f = m_parser.Dequeue())
        {
            Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
            assert(pFrame);  //TODO

            m_context.NotifyAudioFrame(this, pFrame);
        }
    }
       
    return m_context.NotifyAudioEOS(this);
}


void MkvStreamAudioAc3::Flush()
{
    if (m_context.m_file.GetStream())
    {
        m_parser.EndOfStream();

        while (Ac3Parser::Frame* f = m_parser.Dequeue())
        {
            Ac3Frame* const pFrame = new (std::nothrow) Ac3Frame(f);
            assert(pFrame);  //TODO

            m_context.NotifyAudioFrame(this, pFrame);
        }
    }

    MkvStreamAudio::Flush();
}



}  //end namespace MkvMux
